#include "config.h"

#include "ecm.h"

#define ALLOC_CHUNK	1024

typedef struct {
	ecm_uint32	duration;
	ecm_uint64	counts;
	ecm_double	times[2];
	ecm_uint32	alloced_size;
	char		*buf;
	int		ptr;
} perfmsg_t;

typedef struct {
	ECMBOOL		valid;
	ecm_uint64	start, end;
	ecm_double	start_user_time, end_user_time;
	ecm_double	start_sys_time, end_sys_time;
	ecm_uint32	n_counters;
	ecm_uint64	*counters;
} perf_t;

ecm_perf_t
ecm_create_perf(ecm_uint32 n_counters)
{
	perf_t	*perf = ecm_malloc(sizeof(perf_t));

	perf->start = 0;
	perf->end = 0;
	if (n_counters == 0)
		n_counters = 1;
	perf->n_counters = n_counters;
	perf->counters = ecm_calloc(n_counters, sizeof(ecm_uint64));

	return perf;
}

void
ecm_destroy_perf(ecm_perf_t ecm_perf)
{
	perf_t	*perf = (perf_t *)ecm_perf;

	ecm_free(perf);
}

void
ecm_start_perf(ecm_perf_t ecm_perf)
{
	perf_t	*perf = (perf_t *)ecm_perf;
	ecm_tms_t	tms;

	perf->valid = ECM_TRUE;
	perf->start = ecm_tickcount();
	perf->end = 0;
	ecm_memset(perf->counters, 0, sizeof(ecm_uint64) * perf->n_counters);
	ecm_times(&tms);
	perf->start_user_time = tms.user_time;
	perf->start_sys_time = tms.sys_time;
}

void
ecm_stop_perf(ecm_perf_t ecm_perf)
{
	perf_t	*perf = (perf_t *)ecm_perf;
	ecm_tms_t	tms;

	perf->end = ecm_tickcount();
	ecm_times(&tms);
	perf->end_user_time = tms.user_time;
	perf->end_sys_time = tms.sys_time;
}

void
ecm_add_perf_data(ecm_perf_t ecm_perf, ecm_uint32 idx, ecm_uint32 data)
{
	perf_t	*perf = (perf_t *)ecm_perf;

	perf->counters[idx] += (ecm_uint64)data;
}

static void
perfmsg_adjust_buf(perfmsg_t *pmsg, ecm_size_t size)
{
	if (pmsg->alloced_size <= pmsg->ptr + size) {
		pmsg->alloced_size += ALLOC_CHUNK;
		pmsg->buf = ecm_realloc(pmsg->buf, pmsg->alloced_size);
	}
}

static void
perfmsg_add_char(perfmsg_t *pmsg, const char c)
{
	perfmsg_adjust_buf(pmsg, 1);
	pmsg->buf[pmsg->ptr] = c;
	pmsg->ptr++;
}

static void
perfmsg_add_str(perfmsg_t *pmsg, const char *str)
{
	ecm_size_t	size = ecm_strlen(str);

	perfmsg_adjust_buf(pmsg, size);
	ecm_sprintf(pmsg->buf + pmsg->ptr, "%s", str);
	pmsg->ptr += size;
}

static void
insert_counts_per_sec(perfmsg_t *pmsg)
{
	if (pmsg->duration == 0)
		perfmsg_add_str(pmsg, "???");
	else {
		char	*str;

		ecm_asprintf(&str, "%.2f", pmsg->counts * 1000. / pmsg->duration);
		perfmsg_add_str(pmsg, str);
		ecm_free(str);
	}
}

static void
insert_bandwidth(perfmsg_t *pmsg)
{
	static const char	*units[] = {
		"B/sec", "KB/sec", "MB/sec", "GB/sec", "TB/sec", NULL
	};

	if (pmsg->duration == 0)
		perfmsg_add_str(pmsg, "???");
	else {
		double	bandwidth;
		ecm_uint32	i;
		char	*str;

		bandwidth = pmsg->counts * 1000. / pmsg->duration;
		for (i = 0; units[i + 1]; i++, bandwidth /= 1024) {
			if (bandwidth < 1024)
				break;
		}
		ecm_asprintf(&str, "%.2f%s", bandwidth, units[i]);
		perfmsg_add_str(pmsg, str);
		ecm_free(str);
	}
}

static void
insert_time_ratio(perfmsg_t *pmsg, ecm_uint32 idx)
{
	if (pmsg->duration == 0)
		perfmsg_add_str(pmsg, "???");
	else {
		char	*str;

		ecm_asprintf(&str, "%.1f%%", (pmsg->times[idx] * 1000. / pmsg->duration) * 100);
		perfmsg_add_str(pmsg, str);
		ecm_free(str);
	}
}

static void
parse_perf_format(const char *fmt, perfmsg_t *pmsg)
{
	const char	*p;

	for (p = fmt; *p; p++) {
		switch (*p) {
		case '%':
		{
			switch (p[1]) {
			case '%':
				perfmsg_add_char(pmsg, *p);
				break;
			case 'c':
				insert_counts_per_sec(pmsg);
				break;
			case 'b':
				insert_bandwidth(pmsg);
				break;
			case 'u':
				insert_time_ratio(pmsg, 0);
				break;
			case 's':
				insert_time_ratio(pmsg, 1);
				break;
			default:
				break;
			}
			p++;
			break;
		}
		default:
			perfmsg_add_char(pmsg, *p);
			break;
		}
	}
	pmsg->buf[pmsg->ptr] = '\0';
}

static void
init_perfmsg(perfmsg_t *pmsg, perf_t *perf)
{
	ecm_uint32	i;

	pmsg->alloced_size = ALLOC_CHUNK;
	pmsg->buf = ecm_malloc(ALLOC_CHUNK);
	if (!perf->valid)
		pmsg->duration = 0;
	else {
		ecm_uint64	end;

		end = perf->end;
		if (end == 0)
			end = ecm_tickcount(); 
		if (end < perf->start)
			pmsg->duration = 0;
		else
			pmsg->duration = (ecm_uint32)(end - perf->start);
		pmsg->times[0] = perf->end_user_time - perf->start_user_time;
		pmsg->times[1] = perf->end_sys_time - perf->start_sys_time;
	}

	pmsg->counts = 0;
	for (i = 0; i < perf->n_counters; i++)
		pmsg->counts += perf->counters[i];
	pmsg->ptr = 0;
}

static void
clear_perfmsg(perfmsg_t *pmsg)
{
	ecm_free(pmsg->buf);
}

void
ecm_show_perf(ecm_perf_t ecm_perf, FILE *fp, const char *fmt)
{
	perf_t	*perf = (perf_t *)ecm_perf;
	perfmsg_t	perfmsg;

	init_perfmsg(&perfmsg, perf);
	parse_perf_format(fmt, &perfmsg);

	ecm_fprintf(fp, "%s", perfmsg.buf);

	clear_perfmsg(&perfmsg);
}
